/****************************************************************************
*                                                                           *
* Copyright (c) 2009 Nuvoton Tech. Corp. All rights reserved.               *
*                                                                           *
*****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "wblib.h"
#include "w55fa93_gpio.h"
#include "w55fa93_kpi.h"
#include "AviLib.h"
#include "w55fa93_gpio.h"
#include "w55fa93_reg.h"
#include "w55fa93_vpost.h"
#include "SPU.h"
#include "SpiLoader.h"
#include "nvtloader.h"
#include "tag.h"
#ifdef __Security__
#include "Gneiss.h"
#endif

#define E_CLKSKEW	0x00888800

#define __No_RTC__
#define __UPLL_192__

//#define _S605_

#ifdef _S605_	
	#ifdef __Security__
	    #define DATE_CODE   "20181206 with Security for S605"
	#else
		 #define DATE_CODE  "20181206 for S605"
	#endif
#else
	#ifdef __Security__
	    #define DATE_CODE   "20181206 with Security"
	#else
		#define DATE_CODE   "20181206"
	#endif
#endif

#define __DAC_ON__

UINT8 image_buffer[512];
unsigned char *imagebuf;
unsigned int *pImageList;


extern void lcmFill2Dark(unsigned char*);
//unsigned char kbuf[CP_SIZE]; /* save first 16k of buffer. Copy to 0 after vector table is no longer needed */
extern void AudioChanelControl(void);
extern void backLightEnable(void);
int kfd, mfd;
#ifdef __Security__
#define KEY_INDEX 1
void RPMC_CreateRootKey(unsigned char *u8uid, unsigned int id_len, unsigned char *rootkey);
#endif
	
UINT32 u32TimerChannel = 0;
void Timer0_300msCallback(void)
{
#ifdef __BACKLIGHT__
	backLightEnable();
#endif	
	sysClearTimerEvent(TIMER0, u32TimerChannel);
}

#define E_CLKSKEW   0x00888800
#define __DDR_6__

void init(void)
{
	WB_UART_T 	uart;
	UINT32 		u32ExtFreq;	 
	UINT32 u32Cke = inp32(REG_AHBCLK);
	
	/* Reset SIC engine to fix USB update kernel and mvoie file */
	outp32(REG_AHBCLK, u32Cke  | (SIC_CKE | NAND_CKE | SD_CKE)); 
	outp32(REG_AHBIPRST, inp32(REG_AHBIPRST )|SICRST ); 
	outp32(REG_AHBIPRST, 0); 
	outp32(REG_APBIPRST, TMR0RST | TMR1RST); 
	outp32(REG_APBIPRST, 0); 
	outp32(REG_AHBCLK,u32Cke);	
	sysEnableCache(CACHE_WRITE_BACK);
	
    u32ExtFreq = sysGetExternalClock();     /* KHz unit */
    outp32(REG_DQSODS, 0x1010);
    outp32(REG_CKDQSDS, E_CLKSKEW);
    if(u32ExtFreq==12000)
    {
        outp32(REG_SDREF, 0x805A);
    }
    else
    {
        outp32(REG_SDREF, 0x80C0);
    }


#ifdef __UPLL_192__
    if((inp32(REG_CHIPCFG) & SDRAMSEL) == 0x20)     // Power On Setting SDRAM type is DDR2
    {
        outp32(REG_SDMR, 0x432);
        outp32(REG_DQSODS, 0x00001010);
        outp32(REG_MISCPCR,0x00000001);     // Driving strength
        outp32(REG_SDTIME, 0x21667525);
    }
    else if((inp32(REG_CHIPCFG) & SDRAMSEL) == 0x30)     // Power On Setting SDRAM type is DDR
    {
    #ifdef __DDR_75__
        outp32(REG_SDTIME, 0x098E7549); // DDR Speed grade-75
    #endif
    #ifdef __DDR_6__
        outp32(REG_SDTIME, 0x094E7425); // DDR Speed grade-6
    #endif
    #ifdef __DDR_5__
        outp32(REG_SDTIME, 0x094E6425); // DDR Speed grade-5
    #endif
        outp32(REG_SDMR, 0x22);         // Cas Latency = 2
    }

    sysSetSystemClock(eSYS_UPLL,    //E_SYS_SRC_CLK eSrcClk,
                    192000,         //UINT32 u32PllKHz,
                    192000,         //UINT32 u32SysKHz,
                    192000,         //UINT32 u32CpuKHz,
                    192000/2,       //UINT32 u32HclkKHz,
                    192000/4);      //UINT32 u32ApbKHz
#endif

    /* enable UART */
	sysUartPort(1);
	uart.uiFreq = u32ExtFreq*1000;					/* Hz unit */	
	uart.uiBaudrate = 115200;
	uart.uiDataBits = WB_DATA_BITS_8;
	uart.uiStopBits = WB_STOP_BITS_1;
	uart.uiParity = WB_PARITY_NONE;
	uart.uiRxTriggerLevel = LEVEL_1_BYTE;
	sysInitializeUART(&uart);    
	sysprintf("SPI Loader start (%s).\n", DATE_CODE);	
	sysSetLocalInterrupt(ENABLE_IRQ);		
	sysFlushCache(I_D_CACHE);	
	

}

#ifdef _S605_   // support S605 chip
//----------------------------------------------------------------------------
// Initail GPIO pins GPE10 and GPE11 to output mode for S605 module.
//----------------------------------------------------------------------------
void S605_init_gpio()
{
    sysprintf("Initial GPIO pins for S605 module.\n");

    // initial GPE10 to output mode and pull low it.
    outpw(REG_GPEFUN, inpw(REG_GPEFUN) & (~MF_GPE10));      // set GPE10 as GPIO pin
    outpw(REG_GPIOE_PUEN, inpw(REG_GPIOE_PUEN) | BIT10);    // set GPE10 internal resistor to pull up
    outpw(REG_GPIOE_DOUT, inpw(REG_GPIOE_DOUT) & (~BIT10)); // output 0 to GPE10
    outpw(REG_GPIOE_OMD,  inpw(REG_GPIOE_OMD)  | BIT10);    // set GPE10 to OUTPUT mode

    // initial GPE11 to output mode and pull low it.
    outpw(REG_GPEFUN, inpw(REG_GPEFUN) & (~MF_GPE11));      // set GPE11 as GPIO pin
    outpw(REG_GPIOE_PUEN, inpw(REG_GPIOE_PUEN) | BIT11);    // set GPE11 internal resistor to pull up
    outpw(REG_GPIOE_DOUT, inpw(REG_GPIOE_DOUT) & (~BIT11)); // output 0 to GPE11
    outpw(REG_GPIOE_OMD,  inpw(REG_GPIOE_OMD)  | BIT11);    // set GPE11 to OUTPUT mode
}


//----------------------------------------------------------------------------
// Control GPE10 and GPE11 to power on S605 module.
//      When power on, pull low both RST (GPE10 pin) and PMU (GPE11 pin);
//      And then, pull high PMU;
//      Wait 3ms at least, pull high RST;
//      Keep status 12ms at least;
//----------------------------------------------------------------------------
void S605_power_on()
{
    UINT32 u32Delay;

    sysprintf("Power on S605 module.\n");
    outpw(REG_GPIOE_DOUT, inpw(REG_GPIOE_DOUT) & (~BIT10)); // pull low RST
    outpw(REG_GPIOE_DOUT, inpw(REG_GPIOE_DOUT) & (~BIT11)); // pull low PMU
    u32Delay = 20000;
    while(u32Delay--);  // delay 10000 loop ~= 5ms

    outpw(REG_GPIOE_DOUT, inpw(REG_GPIOE_DOUT) | BIT11);    // pull high PMU
    u32Delay = 20000;
    while(u32Delay--);  // delay 10000 loop ~= 5ms

    outpw(REG_GPIOE_DOUT, inpw(REG_GPIOE_DOUT) | BIT10);    // pull hiigh RST
    u32Delay = 40000;
    while(u32Delay--);  // delay 10000 loop ~= 5ms
}
#endif


//The following codes are added to support Linux tag list [2007/03/21]
//currently, only compressed romfs's size and physical address are supported!
int  TAG_create(unsigned int addr, unsigned int size)
{
	static struct tag *tlist;
	
	tlist = (struct tag *) 0x100; //will destroy BIB_ShowInfo()
	
	//tag-list start
//	sysprintf("tlist->hdr.tag = ATAG_CORE;\n");
	tlist->hdr.tag = ATAG_CORE;
    tlist->hdr.size = tag_size (tag_core);
    tlist = tag_next (tlist);

	//tag-list node
    tlist->hdr.tag = ATAG_INITRD2;
    tlist->hdr.size = tag_size (tag_initrd);
    tlist->u.initrd.start = addr;  //romfs starting address 
    tlist->u.initrd.size = size;   //romfs size 
    tlist = tag_next (tlist);

	//tag-list node
 //   tlist->hdr.tag = ATAG_MACADDR;
 //   tlist->hdr.size = tag_size (tag_macaddr);
 ///   memcpy(&tlist->u.macaddr.mac[0], &_HostMAC[0], 6);
    /*    
    uprintf("===>%02x %02x %02x %02x %02x %02x\n", tlist->u.macaddr.mac[0],
                                                   tlist->u.macaddr.mac[1],
                                                   tlist->u.macaddr.mac[2],
                                                   tlist->u.macaddr.mac[3],
                                                   tlist->u.macaddr.mac[4],
                                                   tlist->u.macaddr.mac[5],
                                                   tlist->u.macaddr.mac[6]);
    */                                                   
    tlist = tag_next (tlist);

 	//tag-list end
    tlist->hdr.tag = ATAG_NONE;
    tlist->hdr.size = 0;
    
    return 0;
}

volatile int tag_flag = 0, tagaddr,tagsize;
#ifndef __No_LCM__
static UINT32 bIsInitVpost=FALSE;
LCDFORMATEX lcdInfo;
void initVPostShowLogo(void)
{
	if(bIsInitVpost==FALSE)
	{
		bIsInitVpost = TRUE;
		//lcdInfo.ucVASrcFormat = DRVVPOST_FRAME_YCBYCR;	
		lcdInfo.ucVASrcFormat = DRVVPOST_FRAME_RGB565;	
		lcdInfo.nScreenWidth = PANEL_WIDTH;	
		lcdInfo.nScreenHeight = PANEL_HEIGHT;
		vpostLCMInit(&lcdInfo, (UINT32*)FB_ADDR);
		//backLightEnable();
	}	
}
#endif
void spuDacOnLoader(UINT8 level)
{		
	outp32(REG_AHBCLK, inp32(REG_AHBCLK) &(~( ADO_CKE | SPU_CKE | HCLK4_CKE)));	// disable SPU engine clock 
	outp32(REG_AHBCLK, inp32(REG_AHBCLK) | ADO_CKE | SPU_CKE | HCLK4_CKE);		// enable SPU engine clock 	

	outp32(REG_SPU_CTRL, inp32(REG_SPU_CTRL) & ~SPURST);	
	
	outp32(REG_SPU_CTRL, inp32(REG_SPU_CTRL) | SPURST);
	outp32(REG_SPU_CTRL, inp32(REG_SPU_CTRL) & ~SPURST);	
	
	
	outpw(REG_SPU_DAC_PAR, inpw(REG_SPU_DAC_PAR) | 0x30);		//disable
	if(level == 3)
		outpw(REG_SPU_DAC_PAR, inpw(REG_SPU_DAC_PAR) & ~0x30);	//delay time, p0=3s
	else if(level == 1)
		outpw(REG_SPU_DAC_PAR, inpw(REG_SPU_DAC_PAR) & ~0x20);	//delay time, p0=0.5-1s
	else if(level == 2)
		outpw(REG_SPU_DAC_PAR, inpw(REG_SPU_DAC_PAR) & ~0x10);	//delay time, p0=2s
	else 	
	{	
		outpw(REG_SPU_DAC_VOL, inpw(REG_SPU_DAC_VOL) & ~0x00FF0000);	//P7
		outpw(REG_SPU_DAC_PAR, inpw(REG_SPU_DAC_PAR) | 0x30);		//disable
		return;
	}
#if 0	
	if(level == 3)		//modify this delay time to meet the product request
		_sysDelay(300);
	else if(level == 1)
		_sysDelay(70);
	else if(level == 2)
		_sysDelay(200);
#endif		
}	
int main(void)
{
	unsigned int startBlock;
	unsigned int endBlock;
	unsigned int fileLen;
	unsigned int executeAddr;
	unsigned int start_addr, size;

#ifdef __Security__
	UINT8 	u8UID[8];
	unsigned char ROOTKey[32];	// Rootkey array	
	unsigned char HMACKey[32];	// HMACkey array
	unsigned char HMACMessage[4]; // HMAC message data, use for update HMAC key
	unsigned char Input_tag[12];	// Input tag data for request conte
	unsigned char RPMCStatus;
#endif
	int count, i, j;	
	void	(*fw_func)(void);
	
    outp32(0xFF000000, 0);
	
	if(sysGetChipVersion() == 'G')
		outp32(REG_CLKDIV4, inp32(REG_CLKDIV4)| 0x100);
	
	spuDacOnLoader(2);
	
	outp32(REG_APBCLK, inp32(REG_APBCLK) | RTC_CKE);

	
#ifdef __No_RTC__
	sysprintf("* Not Config RTC\n");	
	outp32(REG_APBCLK, inp32(REG_APBCLK) & ~RTC_CKE);

#else
	if(inp32(INIR) & 0x1)
	{
		sysprintf("* Enable HW Power Off\n");	
		
		count = 0;
		
		outp32(REG_APBCLK, inp32(REG_APBCLK) | RTC_CKE);
	
		outp32(AER,0x0000a965);	 	
	 	
		while(1)
		{
			if((inp32(AER) & 0x10000) ==0x10000)
				break;  		
			if(count > 1000000)
			{
				sysprintf("Write RTC Fail!!\n");
				break;
			}
			count++;
		}
		outp32(PWRON, 0x60005);    	/* Press Power Key during 6 sec to Power off (0x'6'0005) */
	  	outp32(RIIR,0x4);   		
	}
	else
	{
		if((inp32(INIR) & 0x1) == 0)
			sysprintf("RTC is in-active!!\n");	
	}	
 
#endif	
	init();		

#ifdef _S605_   // support S605 chip
    S605_init_gpio();
    S605_power_on();
#endif		
	
#ifndef __No_LCM__			
	initVPostShowLogo();	
#endif
	imagebuf = (UINT8 *)((UINT32)image_buffer | 0x80000000);
	pImageList=((unsigned int *)(((unsigned int)image_buffer)|0x80000000));

	/* Initial DMAC and NAND interface */
	SPI_OpenSPI();
#ifdef __Security__
	if ((RPMC_ReadUID(u8UID)) == -1)
	{
		sysprintf("read id error !!\n");
		return -1;
	}

	sysprintf("SPI flash uid [0x%02X%02X%02X%02X%02X%02X%02X%02X]\n",u8UID[0], u8UID[1],u8UID[2], u8UID[3],u8UID[4], u8UID[5],u8UID[6], u8UID[7]);
  
	/* first stage, initial rootkey */
	RPMC_CreateRootKey((unsigned char *)u8UID,8, ROOTKey);	// caculate ROOTKey with UID & ROOTKeyTag by SHA256

	/* Second stage, update HMACKey after ever power on. without update HMACkey, Gneiss would not function*/
	HMACMessage[0] = rand()%0x100;        // Get random data for HMAC message, it can also be serial number, RTC information and so on.
	HMACMessage[1] = rand()%0x100;
	HMACMessage[2] = rand()%0x100;
	HMACMessage[3] = rand()%0x100;

	/* Update HMAC key and get new HMACKey. 
	HMACKey is generated by SW using Rootkey and HMACMessage.
	RPMC would also generate the same HMACKey by HW */
	RPMCStatus = RPMC_UpHMACkey(KEY_INDEX, ROOTKey, HMACMessage, HMACKey); 	
	if(RPMCStatus == 0x80)
	{
		// update HMACkey success
		sysprintf("RPMC_UpHMACkey Success - 0x%02X!!\n",RPMCStatus );
	}
	else
	{
		// write HMACkey fail, check datasheet for the error bit
		sysprintf("RPMC_UpHMACkey Fail - 0x%02X!!\n",RPMCStatus );
	}

	/* Third stage, increase RPMC counter */  
	/* input tag is send in to RPMC, it could be time stamp, serial number and so on*/
	for(i= 0; i<12;i++)
		Input_tag[i] = u8UID[i%8];
	
	RPMCStatus = RPMC_IncCounter(KEY_INDEX, HMACKey, Input_tag);	
	if(RPMCStatus == 0x80){
		// increase counter success
		sysprintf("RPMC_IncCounter Success - 0x%02X!!\n",RPMCStatus );
	}
	else{
		// increase counter fail, check datasheet for the error bit
		sysprintf("RPMC_IncCounter Fail - 0x%02X!!\n",RPMCStatus );
		while(1);
	}
			
	if(RPMC_Challenge(KEY_INDEX, HMACKey, Input_tag)!=0)
	{
		sysprintf("RPMC_Challenge Fail!!\n" );
		/* return signature miss-match */
		while(1);
	}	
	else	
		sysprintf("RPMC_Challenge Pass!!\n" );
#endif		
	memset(imagebuf, 0, 32);
	sysprintf("Load Image ");
	/* read image information */
	SPIReadFast(0, 63*1024, 512, (UINT32*)imagebuf);  /* offset, len, address */

	if (((*(pImageList+0)) == 0xAA554257) && ((*(pImageList+3)) == 0x63594257))
	{
		count = *(pImageList+1);

		pImageList=((unsigned int*)(((unsigned int)image_buffer)|0x80000000));
		startBlock = endBlock = fileLen = executeAddr = 0;
		
		/* load logo first */
		pImageList = pImageList+4;
		for (i=0; i<count; i++)
		{
			if (((*(pImageList) >> 16) & 0xffff) == 4)	// logo
			{
				startBlock = *(pImageList + 1) & 0xffff;
				endBlock = (*(pImageList + 1) & 0xffff0000) >> 16;
				executeAddr = *(pImageList + 2);
				fileLen = *(pImageList + 3);
				SPIReadFast(0, startBlock * 0x10000, fileLen, (UINT32*)executeAddr);						
				break;
			}
			/* pointer to next image */
			pImageList = pImageList+12;
		}

		pImageList=((unsigned int*)(((unsigned int)image_buffer)|0x80000000));
		startBlock = endBlock = fileLen = executeAddr = 0;

		/* load romfs file */
		pImageList = pImageList+4;
		for (i=0; i<count; i++)
		{
			if (((*(pImageList) >> 16) & 0xffff) == 2)	// RomFS
			{
				startBlock = *(pImageList + 1) & 0xffff;
				endBlock = (*(pImageList + 1) & 0xffff0000) >> 16;
				executeAddr = *(pImageList + 2);
				fileLen = *(pImageList + 3);
				SPIReadFast(0, startBlock * 0x10000, fileLen, (UINT32*)executeAddr);
				tag_flag = 1;
				tagaddr = executeAddr;
				tagsize = fileLen;
				
				break;
			}
			/* pointer to next image */
			pImageList = pImageList+12;
		}

		pImageList=((unsigned int*)(((unsigned int)image_buffer)|0x80000000));
		startBlock = endBlock = fileLen = executeAddr = 0;

		/* load execution file */
		pImageList = pImageList+4;
		for (i=0; i<count; i++)
		{
			if (((*(pImageList) >> 16) & 0xffff) == 1)	// execute
			{
				startBlock = *(pImageList + 1) & 0xffff;
				endBlock = (*(pImageList + 1) & 0xffff0000) >> 16;
				executeAddr = *(pImageList + 2);
				fileLen = *(pImageList + 3);

				sysSetGlobalInterrupt(DISABLE_ALL_INTERRUPTS);
				sysSetLocalInterrupt(DISABLE_FIQ_IRQ);							
#ifdef __DAC_ON__

				outpw(REG_SPU_DAC_VOL, inpw(REG_SPU_DAC_VOL) & ~0x0800000);	//P7	
				
				start_addr = startBlock * 0x10000;
				
				size = (fileLen >> 10) << 8;
				
								
			    for(j=0;j<4;j++)
				{
					SPIReadFast(0, start_addr + size * j, size, (UINT32*) (executeAddr + size * j));

					switch(j)
					{
						case 0:;
							outpw(REG_SPU_DAC_VOL, inpw(REG_SPU_DAC_VOL) & ~0x0400000);	//P6
							break;
						case 1:							
							outpw(REG_SPU_DAC_VOL, inpw(REG_SPU_DAC_VOL) & ~0x01e0000);	//P1-4
							break;
						case 2:
			             	outpw(REG_SPU_DAC_VOL, inpw(REG_SPU_DAC_VOL) & ~0x0200000);	//P5	
	        				break;
					}
				}
				fileLen = fileLen - size * 4;
				if(fileLen)
					SPIReadFast(0, start_addr + size * 4, fileLen, (UINT32*) (executeAddr + size * 4));

            	outpw(REG_SPU_DAC_VOL, inpw(REG_SPU_DAC_VOL) & ~0x00010000);	//P0			
	
	            outpw(REG_SPU_DAC_VOL, inpw(REG_SPU_DAC_VOL) | 0x00001F1F);	
	            
 	            outp32(REG_AHBCLK, inp32(REG_AHBCLK) | ADO_CKE | SPU_CKE | HCLK4_CKE);		// enable SPU engine clock 					
	            /* Initial SPU in advance for linux set volume issue */ 	
	            spuOpen(eDRVSPU_FREQ_8000);
#else	            
				SPIReadFast(0, startBlock * 0x10000, fileLen, (UINT32*)executeAddr);
#endif		

				sysSetGlobalInterrupt(DISABLE_ALL_INTERRUPTS);
				sysSetLocalInterrupt(DISABLE_FIQ_IRQ);		
				// Invalid and disable cache
				sysDisableCache();
				sysInvalidCache();
				
				if(tag_flag)
				{
					sysprintf("Create Tag - Address 0x%08X, Size 0x%08X\n",tagaddr,tagsize );
					TAG_create(tagaddr,tagsize);				
				}								
				
				// JUMP to kernel
				sysprintf("Jump to kernel\n\n\n");

				
				//lcmFill2Dark((char *)(FB_ADDR | 0x80000000));	
				outp32(REG_AHBIPRST, JPGRST | SICRST |UDCRST | EDMARST);
				outp32(REG_AHBIPRST, 0);
				outp32(REG_APBIPRST, UART1RST | UART0RST | TMR1RST | TMR0RST );
				outp32(REG_APBIPRST, 0);
				sysFlushCache(I_D_CACHE);	   	
				
					
				fw_func = (void(*)(void))(executeAddr);
				fw_func();				
				break;
			}
			/* pointer to next image */
			pImageList = pImageList+12;
		}
	}	
	
	return(0); // avoid compilation warning
}